package com.ys.erp.framework.response;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.ys.erp.common.enums.RedisKey;
import com.ys.erp.framework.annotation.CacheResult;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;

/**
 * @author lwj
 * @date 2025/1/11
 */
@Aspect
@Component
@RequiredArgsConstructor
public class CacheAspect {

    private final RedisTemplate<String, String> redisTemplate;

    @Pointcut("@annotation(com.ys.erp.framework.annotation.CacheResult)")
    public void cacheableMethods() {}

    @Around("cacheableMethods()")  // 环绕通知
    public Object cacheResult(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取目标方法的签名
        Signature signature = joinPoint.getSignature();
        // 获取方法签名对象（MethodSignature）
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        // 获取方法上的 @CacheResult 注解
        CacheResult cacheResultAnnotation = method.getAnnotation(CacheResult.class);
        // 获取注解中的值
        String cacheKey = cacheResultAnnotation.key();
        int cacheHours = cacheResultAnnotation.hours();
        // 获取请求路径和请求体参数作为缓存key
        String key = null;
        if (ObjectUtils.isNotEmpty(cacheKey)) {
            key = cacheKey;
        } else {
            key = generateCacheKey(joinPoint, method);
        }
        String cacheResult = redisTemplate.opsForValue().get(key);
        if (ObjectUtils.isNotEmpty(cacheResult)) {
            // 获取目标方法的返回类型
            Class<?> returnType = methodSignature.getReturnType();
            // 将JSON字符串转换为目标返回类型的对象
            return JSONObject.parseObject(cacheResult, returnType);
        }
        // 如果缓存中没有数据，调用目标方法并获取结果
        Object result = joinPoint.proceed();
        redisTemplate.opsForValue().set(key, JSONObject.toJSONString(result), cacheHours, TimeUnit.HOURS);
        return result;
    }

    // 根据请求路径和请求体参数生成缓存key
    private String generateCacheKey(ProceedingJoinPoint joinPoint, Method method) {
        // 获取请求路径
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String path = request.getRequestURI();
        // 生成请求体参数的字符串
        // 获取方法的所有参数
        Object[] args = joinPoint.getArgs();
        String param = "";
        // 获取参数的注解信息
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                // 如果参数有 @RequestBody 注解
                if (annotation instanceof RequestBody) {
                    // 获取被 @RequestBody 修饰的参数
                    Object requestBody = args[i];
                    param = JSONObject.toJSONString(requestBody, JSONWriter.Feature.WriteMapNullValue);
                }
            }
        }
        // 缓存key为请求路径+请求体参数的组合
        String key = path + ":" + param;
        return MessageFormat.format(RedisKey.Enums.CONTROLLER_RESULT_CACHE.getKey(), key);
    }
}
